﻿// <copyright file="ChartScaleControl.cs" company="Oleg V. Polikarpotchkin">
// Copyright © 2008-2009 Oleg V. Polikarpotchkin. All Right Reserved
// </copyright>
// <author>Oleg V. Polikarpotchkin</author>
// <email>ov-p@yandex.ru</email>
// <date>2009-02-18</date>
// <summary>OpenWPFChart library. ChartScaleControl to edit ChartScale's.</summary>
// <revision>$Id$</revision>

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using OpenWPFChart.Parts;

namespace OpenWPFChart.Helpers
{
	/// <summary>
	/// Interaction logic for ChartScaleControl.xaml
	/// </summary>
	public partial class ChartScaleControl : UserControl, INotifyPropertyChanged
	{
		/// <summary>
		/// Initializes a new instance of the <see cref="ChartScaleControl"/> class.
		/// </summary>
		public ChartScaleControl()
		{
			InitializeComponent();
		}

		#region ChartScale
		/// <summary>
		/// ChartScaleProperty DependencyProperty
		/// </summary>
		public static readonly DependencyProperty ChartScaleProperty =
			DependencyProperty.Register("ChartScale", typeof(ChartScale), typeof(ChartScaleControl)
				, new FrameworkPropertyMetadata(null
					, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault
					, ChartScalePropertyChanged));
		/// <summary>
		/// Gets or sets the ChartScale property.
		/// </summary>
		/// <value>ChartScale derived object or <c>null</c>.</value>
		public ChartScale ChartScale
		{
			get { return (ChartScale)GetValue(ChartScaleProperty); }
			set { SetValue(ChartScaleProperty, value); }
		}

		/// <summary>
		/// ChartScale Dependency Property changed.
		/// </summary>
		/// <param name="d">The sender.</param>
		/// <param name="e">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> 
		/// instance containing the event data.</param>
		static void ChartScalePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			ChartScaleControl ths = d as ChartScaleControl;
			if (ths == null)
				return;
			ChartScale oldScale = e.OldValue as ChartScale;
			if (oldScale != null)
				oldScale.PropertyChanged -= ths.ChartScalePropertyChanged;

			ChartScale newScale = e.NewValue as ChartScale;
			if (newScale == null)
				ths.ChartScaleType = null;
			else
			{
				Debug.Assert(newScale.IsConsistent, "newScale.IsConsistent");
				newScale.PropertyChanged += ths.ChartScalePropertyChanged;
				ths.ChartScaleType = newScale.GetType();
			}
			ths.NotifyPropertyChanged("ChartScale");

			ths.UpdateFullChartScaleGroup();
			ths.UpdateChartScaleGroup();
		}

		/// <summary>
		/// ChartScale property changed.
		/// </summary>
		/// <param name="sender">The sender.</param>
		/// <param name="e">The <see cref="System.ComponentModel.PropertyChangedEventArgs"/> instance containing the event data.</param>
		void ChartScalePropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			NotifyPropertyChanged(e.PropertyName);
			UpdateChartScaleGroup();
		}
		#endregion ChartScale

		#region CanChangeChartScaleType
		/// <summary>
		/// CanChangeChartScaleTypeProperty DependencyProperty
		/// </summary>
		public static readonly DependencyProperty CanChangeChartScaleTypeProperty =
			DependencyProperty.Register("CanChangeChartScaleType", typeof(bool), typeof(ChartScaleControl)
				, new FrameworkPropertyMetadata(true, CanChangeChartScaleTypePropertyChanged));
		/// <summary>
		/// Gets or sets a value indicating whether ChartScale Type can be changed.
		/// </summary>
		/// <value>
		/// 	<c>true</c> if ChartScale Type can be changed; otherwise, <c>false</c>.
		/// </value>
		[TypeConverter("BooleanConverter")]
		public bool CanChangeChartScaleType
		{
			get { return (bool)GetValue(CanChangeChartScaleTypeProperty); }
			set { SetValue(CanChangeChartScaleTypeProperty, value); }
		}

		/// <summary>
		/// CanChangeChartScaleType Dependency Property changed.
		/// </summary>
		/// <param name="d">The sender.</param>
		/// <param name="e">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> 
		/// instance containing the event data.</param>
		static void CanChangeChartScaleTypePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			ChartScaleControl ths = d as ChartScaleControl;
			if (ths == null)
				return;
			ths.NotifyPropertyChanged("CanChangeChartScaleType");
			ths.UpdateChartScaleGroup();
		}
		#endregion CanChangeChartScaleType

		#region ChartScaleType
		Type chartScaleType;
		/// <summary>
		/// Gets or sets the type of the current <see cref="P:OpenWPFChart.Helpers.ChartScaleControl.ChartScale"/> 
		/// property value.
		/// </summary>
		/// <value>The type of the current <see cref="P:OpenWPFChart.Helpers.ChartScaleControl.ChartScale"/> 
		/// property value or <c>null</c>.</value>
		public Type ChartScaleType
		{
			get { return chartScaleType; }
			set
			{
				if (chartScaleType != value)
				{
					chartScaleType = value;
					NotifyPropertyChanged("ChartScaleType");

					if (ChartScale != null && ChartScale.GetType() != chartScaleType)
					{ // Update the ChartScale.
						ChartScale = ChartScale.Create(chartScaleType);
					}
				}
			}
		}
		#endregion ChartScaleType

		#region ChartScaleGroup
		/// <summary>
		/// All types in the current ChartScale Group.
		/// </summary>
		List<Type> fullChartScaleGroup = new List<Type>();
		/// <summary>
		// The list of ChartScale types which could be interchanged with the current ChartScale type.
		/// </summary>
		List<Type> chartScaleGroup = new List<Type>();
		/// <summary>
		/// Gets or sets the Chart Scale Group.
		/// </summary>
		/// <value>The list of types in the Chart Scale Group current
		/// <see cref="P:OpenWPFChart.Helpers.ChartScaleControl.ChartScale"/> belonges to.</value>
		public List<Type> ChartScaleGroup
		{
			get { return chartScaleGroup; }
			set
			{
				Debug.Assert(value != null, "value != null");
				bool different = false;
				if (chartScaleGroup.Count == value.Count)
				{
					foreach (Type type in chartScaleGroup)
					{
						if (!value.Contains(type))
						{
							different = true;
							break;
						}
					}
				}
				else
					different = true;

				if (different)
				{
					chartScaleGroup = value;
					NotifyPropertyChanged("ChartScaleGroup");
				}
			}
		}

		/// <summary>
		/// Updates full chart scale group.
		/// </summary>
		void UpdateFullChartScaleGroup()
		{
			List<Type> group = new List<Type>();
			if (ChartScale == null)
			{
				fullChartScaleGroup = group;
				return;
			}
			if (fullChartScaleGroup.Contains(ChartScale.GetType()))
				return;

			// Get the ChartScale ChartScaleGroupAttribute
			object[] attributes = ChartScale.GetType().GetCustomAttributes(typeof(ChartScaleGroupAttribute), false);
			if (attributes == null || attributes.Length != 1 || !(attributes[0] is ChartScaleGroupAttribute))
			{
				fullChartScaleGroup = group;
				return;
			}
			ChartScaleGroupAttribute groupAttribute = attributes[0] as ChartScaleGroupAttribute;

			// Get all types from the same ChartScale Group in the Group TagType defining assembly.
			FillChartScaleGroup(groupAttribute.TagType.Assembly, groupAttribute.TagType, group);
			if (ChartScale.GetType().Assembly != groupAttribute.TagType.Assembly)
				// Get all types from the same ChartScale Group in the ChartScale defining assembly.
				FillChartScaleGroup(ChartScale.GetType().Assembly, groupAttribute.TagType, group);

			// Update fullChartScaleGroup field only if the new and old groups differ.
			if (fullChartScaleGroup.Count == group.Count)
			{
				foreach (Type type in fullChartScaleGroup)
				{
					if (!group.Contains(type))
					{
						fullChartScaleGroup = group;
						return;
					}
				}
			}
			else
				fullChartScaleGroup = group;
		}

		/// <summary>
		/// Fills the chart scales group with types from the <paramref name="assembly"/> specified.
		/// </summary>
		/// <param name="assembly">The assembly.</param>
		/// <param name="groupTagType">Type of the group tag.</param>
		/// <param name="group">The group.</param>
		static void FillChartScaleGroup(Assembly assembly, Type groupTagType, List<Type> group)
		{
			foreach (Type type in assembly.GetTypes())
			{
				if (!type.IsDefined(typeof(ChartScaleGroupAttribute), false))
					continue;

				object[] attributes = type.GetCustomAttributes(typeof(ChartScaleGroupAttribute), false);
				if (attributes == null || attributes.Length != 1 || !(attributes[0] is ChartScaleGroupAttribute))
					continue;

				ChartScaleGroupAttribute attribute = attributes[0] as ChartScaleGroupAttribute;
				if (groupTagType == attribute.TagType && !group.Contains(type))
					group.Add(type);
			}
		}

		/// <summary>
		/// Updates chart scale group.
		/// </summary>
		/// <remarks>
		/// </remarks>
		void UpdateChartScaleGroup()
		{
			if (!ChartScale.IsConsistent || !CanChangeChartScaleType)
			{ // Forbid to change the ChartScale type when the current ChartScale isn't consistent.
				ChartScaleGroup = new List<Type>() { ChartScale.GetType() };
				return;
			}

			// Fill the group list with ChartScale types which could be interchanged with 
			// the current ChartScale type.
			List<Type> group = new List<Type>();
			foreach (Type type in fullChartScaleGroup)
			{
				if (ChartScale.GetType() == type || ChartScale.Create(type) != null)
					group.Add(type);
			}
			ChartScaleGroup = group;
		}
		#endregion ChartScaleGroup

		#region INotifyPropertyChanged Members
		public event PropertyChangedEventHandler PropertyChanged;
		protected void NotifyPropertyChanged(string propertyName)
		{
			if (PropertyChanged != null)
				PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
		}
		#endregion INotifyPropertyChanged Members
	}
}
